package com.google.code.jholidays.core;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

import com.google.code.jholidays.events.AbstractEvent;
import com.google.code.jholidays.events.DependentEventFactory;
import com.google.code.jholidays.events.FixedEventFactory;
import com.google.code.jholidays.events.FloatingEventFactory;
import com.google.code.jholidays.events.IEvent;
import com.google.code.jholidays.exceptions.NotSupportedEventException;
import com.google.code.jholidays.io.IDescriptorReader;
import com.google.code.jholidays.io.csv.CsvReader;
import com.google.code.jholidays.io.jdbc.JdbcReader;
import com.google.code.jholidays.io.xml.XmlReader;

/**
 * Core component in library. Builds {@link IEvent} instances using
 * {@link DescriptorCollection}.
 * <p>
 * In order to create events, descriptors collection should be created first.
 * Common practice is to use concrete implementation of
 * {@link IDescriptorReader} interface. Descriptors collection can be stored in
 * <tt>CSV</tt> file so {@link CsvReader} can be used to read it from file.
 * Other possible formats are: <tt>XML</tt> and <tt>JDBC source</tt>. See
 * {@link XmlReader} and {@link JdbcReader} for additional information.
 * <p>
 * After {@link DescriptorCollection} is created it can be passed to
 * {@link #buildEvents(DescriptorCollection)} method which will create events
 * list using this descriptors collection
 * 
 * @see EventDescriptor
 * @see IEvent
 * @see AbstractEvent
 * @see IDescriptorReader
 * @author tillias
 * 
 */
public class EventBuilder {

    public EventBuilder() {
	initialize();
    }

    /**
     * Builds {@link IEvent} list using given descriptors collection
     * 
     * @param descriptors
     *            Descriptors collection which will be used to build
     *            {@link IEvent} list
     * @return Events list
     * @throws IllegalArgumentException
     *             Thrown if descriptors collection isn't consistent
     * @throws NotSupportedEventException
     *             Thrown if any descriptor represents illegal event that can't
     *             be recognized by event builder
     * 
     */
    public List<IEvent> buildEvents(DescriptorCollection descriptors)
	    throws IllegalArgumentException, NotSupportedEventException {
	List<IEvent> result = new ArrayList<IEvent>();

	if (descriptors != null) {
	    if (descriptors.isConsistent()) {

		HashMap<Integer, IEvent> builtEvents = new HashMap<Integer, IEvent>();
		Queue<EventDescriptor> queue = new LinkedList<EventDescriptor>(
			descriptors);

		while (queue.size() > 0) {
		    EventDescriptor current = queue.poll();

		    if (current.isRoot()) {
			IEvent event = createEvent(current, null);
			builtEvents.put(event.getID(), event);
		    } else if (builtEvents.containsKey(current.getParentID())) {
			IEvent parentEvent = builtEvents.get(current
				.getParentID());
			IEvent event = createEvent(current, parentEvent);
			builtEvents.put(event.getID(), event);
		    } else {
			queue.add(current);
		    }
		}

		// generate output list of events
		Collection<IEvent> collection = builtEvents.values();
		for (IEvent e : collection) {
		    result.add(e);
		}
	    } else
		throw new IllegalArgumentException(
			"Descriptors collection isn't consistent.");
	}

	return result;
    }

    private boolean initialize() {
	compositeFactory = new CompositeFactory();

	initBuiltInEvents(compositeFactory);

	initPluginEvents(compositeFactory);

	return true;
    }

    private void initBuiltInEvents(CompositeFactory factory) {
	if (factory != null) {
	    try {
		factory.add(new FixedEventFactory());
		factory.add(new DependentEventFactory());
		factory.add(new FloatingEventFactory());
		// TODO:
	    } catch (Exception e) {
		// nothing todo here
	    }
	}
    }

    private void initPluginEvents(CompositeFactory factory) {
	if (factory != null) {
	    try {
		// TODO:
	    } catch (Exception e) {
		// nothing todo here
	    }
	}
    }

    private IEvent createEvent(EventDescriptor descriptor, IEvent parent)
	    throws NotSupportedEventException {
	IEvent result = compositeFactory.createEvent(descriptor, parent);

	if (result == null)
	    throw new NotSupportedEventException(
		    "Event can't be built using given descriptor. Invalid or not supported descriptor.",
		    descriptor);

	return result;
    }

    private CompositeFactory compositeFactory;
}
